/*
	Script: Choqolate
	The live WebUI.
	
	Author:
		Mac Duy Hai
		
	Version: 
		0.1
		
	Requirements:
		- MooTools 1.11
		
*/

/*
	Section: Todo
	'Overlays' - support for dialog boxes, etc.
*/

var Choqolate = {
	version: "0.1"
};


/*	Class: Base
	Base elements that make up some of more advanced widgets
*/
Choqolate.Base = {};

/*
	Class: Base.AjaxOutput
	An object that takes control of an Element, to receive ajax responses etc.
	
	Arguments:
		el - the output Element().
		
	Events:
		AjaxOutput fires various events on different occasions. These can be fully customized by the user:
		o onReceive
		o onCancel
		o onFinished
		o onRig
*/
Choqolate.Base.AjaxOutput = new Class({
	/*
		Section: Properties
		output	- The actual Element(), where ajax response is dumped.
		cancel  - The Element() that controls the Cancel action.

		id		- Data Identification, for data manipulation and operation.
	*/
	output	: Element(),
	rigged	: false,
	inForm	: false,
	id		: -1,	// DO NOT CHANGE!!! editor.lib depends on this...

	initialize: function(el) {
		this.output = el;
		el.ajaxOutput = this;
	},
	
	// Function: rig
	// prepares the row to receive ajax response
	//
	// Returns:
	//	  The output Element(), where the response should be 
	rig: function() {
		// TODO: display ticker in overlay
		this.rigged = true;
		this.fireEvent('onRig');
		return this.output;
	},
	
	// Function: getHeight
	// returns the height of the output in _px_
	getHeight: function() {
		return this.output.getSize().size.y;
	},
	
	// Function: getElement
	// returns the actual output element. Returns null if not rigged.
	getOutput: function() {
		if (this.rigged) return this.output; else return null;
	},
	
	// Function: receive
	// updates the output element with the result passed.
	//
	// Arguments:
	//		result - Response/HTML to be put into
	receive: function(result) {
		
		this.output.empty();
		
		if (this.inForm) {
			// remove form tags
			tmp = new Element('div');
			tmp.setHTML(result);
			tmp.getElements('form').each(function(frm){
				frmRep = new Element('div');
				frmRep.setHTML(frm.innerHTML);
				frmRep.injectAfter(frm);
				frm.remove();
			});
			result = tmp.innerHTML;
		};
		this.output.setHTML(result);
		// fix png for ie6
		//if(window.ie6)
			//this.output.getElements('img.fixPNG').each(fixPNG);
		//this.output.getElements('button').each( function(btn) {btn.setStyle('width', btn.getSize().size.x + 20) });
		
		this.onReceive();
	},
	
	onReceive: function() {
		this.output.effect('opacity', {duration: 1000}).start(1);
		this.fireEvent("onReceive");
	},
	onFinish: function() {
		this.output.effect('opacity').start(0);
		this.fireEvent("onFinish");
		this.rigged = false;
	},
	onCancel: function() {
		this.output.effect('opacity').start(0);
		this.fireEvent("onCancel");
		this.rigged = false;
	}
});
Choqolate.Base.AjaxOutput.implement(new Events);
Choqolate.Base.AjaxOutput.implement(new Options);

/*
	Class: Base.AjaxWrapped
	An extended version of AjaxOutput, which receives a wrapper instead of the actual output Element()
	
	Extends:
		<Base.AjaxOutput>
	
	Arguments:
		el - the wrapper Element(). AjaxWrapped will automatically create a cancel button and an output div to receive 
			 ajax responses
		'o' - options, see below
		
	Options:
		*output*: settings for the output
			style - Either CSS Class or a object containing CSS style settings
		*cancel*: settings for the cancel/close button
			text - text to put on the button. Default: 'Close'
			style - Either CSS Class or a object containing CSS style settings
		*minimize*: settings for the maximize button
			text - text to show on the minimize button
			style - styles to apply the button
			class - CSS class
		*maximize*: settings for the maximize button, as above
	
	Events:
		Apart from those events in <Base.AjaxOutput>, AjaxWrapped also fires these events
		o onMinimize
		o onMaximize
*/
Choqolate.Base.AjaxWrapped = Choqolate.Base.AjaxOutput.extend({
	/*
		Section: Properties
			In addition to those in <Base.AjaxOutput> we have
			
				wrapper - The Element() that is wrapped around the output. 
						  Initial argument when constructing this object
				cancel  - The Element() that controls the Cancel action.
	*/
	wrapper  : Element(),
	cancel	 : Element(),
	minimize : Element(),
	minimized: false,
	options  : {
		output: {
			style: {
				'style': 'opacity: 0;',
				'id': 'LiveRow-' + this.id
			}
		},
		cancel: {
			text: 'Close',
			style: {},
			'class': ''
		},
		minimize: {
			text: 'Minimize',
			style : {},
			'class': ''
		},
		maximize: {
			text: 'Maximize',
			style: {},
			'class': ''
		}
	},

	initialize: function(el,o) {
		this.wrapper = el;
		this.setOptions(o);
	},
	
	// Function: rig
	// prepares the row to receive ajax response
	//
	// Returns:
	//	  The output Element(), where the response should be 
	rig: function() {
		if (!this.rigged) {
			// output
			this.output = new Element('div', {'id': 'LiveRow-' + this.id});
			if ($type(this.output.style) == 'object') this.output.setStyles(this.options.output.style);
				else this.output.addClass(this.options.output.style);
				
			// Bind
			this.output.ajaxOutput = this;
			
			// Cancel button
			this.cancel = new Element('a');
			this.cancel.setText(this.options.cancel.text);
			this.cancel.setStyles(this.options.cancel.style);
			this.cancel.addClass(this.options.cancel.class);
			this.cancel.addEvent('click', function() {
				this.onCancel();
			}.bind(this));
			
			// Minimize button
			this.minimize = new Element('a');
			this.minimize.setText(this.options.minimize.text);
			this.minimize.addEvent('click', function() {
				if (this.minimized) this.onMaximize(); else this.onMinimize();
			}.bind(this));
			this.onMaximize();			// sets minimize button defaults

			
			// Combine
			this.wrapper.adopt(this.cancel);
			this.wrapper.adopt(this.minimize);
			this.wrapper.adopt(this.output);
			this.rigged = true;
			this.minimized = false;
		} else {
			this.cancel.injectTop(this.wrapper);
		}
		this.fireEvent('onRig');
		return this.output;
	},
	
	getHeight: function() {
		// overloaded redefinition for wrapped ajax output. Returns the expected height of the wrapper
		return (this.getMinimizedHeight() + this.parent());
	},
	
	// Function: getMinimizedHeight
	// Returns the expected height of the wrapper for 
	getMinimizedHeight: function() {
		return (this.output.getPosition().y - this.wrapper.getPosition().y);
	},
	
	// Function: onMinimize
	// Specific for AjaxWrapped and those who derive from it
	onMinimize: function() {
		this.minimized = true;
		this.minimize.addClass(this.options.maximize.class);
		this.minimize.setStyles(this.options.maximize.class);
		this.minimize.removeClass(this.options.minimize.class);
		this.fireEvent('onMinimize');
	},
	
	// Function: onMaximize
	// Specific for AjaxWrapped and those that derive from it
	onMaximize: function() {
		this.minimized = false;
		this.minimize.removeClass(this.options.maximize.class);
		this.minimize.setStyles(this.options.minimize.class);
		this.minimize.addClass(this.options.minimize.class);
		this.fireEvent('onMaximize');
	}
});


/*	Class: Base.LiveRow
	Controls and manages the ajax behaviour of a single row in a LiveTable.
	
	Extends:
		<Base.AjaxWrapped>
		
	See Also:
		<LiveTable>
*/
Choqolate.Base.LiveRow = Choqolate.Base.AjaxWrapped.extend({

	/*
		Section: Properties
		table	- link to the parent <LiveTable> object
		dataRow - Element("tr") that contains the data displayed
		ajaxRow - Element("tr") where ajax related elements reside

		in addition to those above, LiveRow inherits the basic variables from <Base.AjaxOutput>.
	*/
	table	: Choqolate.LiveTable,
	dataRow	: Element(),
	ajaxRow	: Element(),
	cell	: Element(),
	id		: -1,
	
	initialize: function(table, row, o) {
		this.setOptions(o);
		// store tr
		this.dataRow = row;
		this.table = table;
		
		this.inForm = true;
		
		// retrieve id value
		if ($defined(this.table.options.idColumnCSS)) {
			this.id = $E(this.table.options.idColumnCSS, this.dataRow).getText();
		}
		
		// button back link
		var that = this;
		this.dataRow.getElements('button').each( function(btn) {
			btn.row = that;
		});
	},
	
	/*
		Function: rig
		Prepares the row for ajax request. If needed, it will create the necessary HTML components to hold the ajax result
		and further assist the ajax operations. Makes a call to its parent function <AjaxOutput.rig>
	*/
	rig: function() {
		if (!this.rigged) {
			var that = this;
			this.ajaxRow = new Element('tr', {
				'style': 'height: 10px'
			});
			this.cell = new Element('td', {
				'colspan'	: this.table.width,
				'class'		: 'ajaxRow throbber'
			});
			this.wrapper = new Element('div', {
				'style': 'overflow: hidden; height: 100%; background:'
			});

			// Join together
			this.cell.adopt(this.wrapper);
			this.ajaxRow.adopt(this.cell);

			// slide out ajax area
			this.ajaxRow.injectAfter(this.dataRow);			
			this.cell.effect('height').start(50);
			
			// call the parent to rig remaining elements. Also sets 'rigged' to true
			this.parent();
		}
	},
	
	/* 	Function: update
		Receives a update "package" and updates the cells
	*/
	update: function(obj) {
		// update cells
		up = new Hash(obj);
		this.dataRow.getElements("td").each( function(cell, i) {
			if (up.hasKey(i)) cell.setText(up.get(i));
		});
		
		// recheck id value
		if($defined(obj[0])){
			if(obj[0] != this.id){
				// update buttons
				this.id = obj[0];
				this.table.fireEvent('onRowUpdate', this);
			}
		}
	},
	
	close: function() {
		this.onCancel();
	},
	
	/*
		Section: Events
		onFinish - currently same as onCancel
		onReceive - invoked when the output has been filled with ajax response. *Called automatically* when calling <AjaxOutput.receive>
		onCancel - called when the Cancel element is pressed
	*/
	onFinish: function() {
		this.onCancel();
	},
	
	onReceive: function() {
		this.parent();
		this.cell.removeClass("throbber");
		// undim the output and expand cell to proper height
		this.cell.effect().stop();
		this.cell.effect('height').start( this.getHeight() );
	},
	
	onCancel: function() {
		this.parent();
		this.output.effect('opacity').start(0);
		this.cell.effect('height').start(0).chain(function() {
			this.ajaxRow.remove();
		}.bind(this));
	},
	
	onMaximize: function() {
		this.parent();
		this.cell.effect('height').start(this.getHeight());
	},
	
	onMinimize: function() {
		this.parent();
		this.cell.effect('height').start(this.getMinimizedHeight());
	}
});

/*	Class: LiveTable
	Creates a LiveTable. The container element *must* contain at least one table. The first table found will be rigged, others are ignored.
	
	Arguments:
		container 	- Container element.
		options		- Collection of options, see below
		rowOptions	- Row options, see <Base.LiveRow> and <Base.AjaxWrapped>
		
	Options:
		deleteFirstColumn - set to true if you want to delete the first column of the table
		idColumnCSS		  - CSS identifying the column from which to retrieve the id value. Can be later used for ajax.
		buttonCSS		  - CSS identifying the buttons to be rigged.
		
	Example:
		> var lt = new Choqolate.LiveTable($('container'), {
		> 	deleteFirstColumn: true,
		>	buttonCSS  : 'button',
		>	idColumnCSS: '.col-1'			
		> });
*/
Choqolate.LiveTable = new Class({
	width		: 0,
	rows		: [],
	options		: {},
	table		: Element(),
	sorts		: {
		last: -1,
		asc: true
	},

	// removes the first column (usually checkboxes)
	removeFirstColumn: function(table) {

		$ES('tr', table).each( function(row) {
			row.getFirst().remove();
		});
	},
	
	/* 	Function: rigRow
		Prepares the ith row for ajax request.
		
		Arguments:
			id - row ID
	*/
	rigRow: function(id) {
		row = this.findRow(id);
		if ($defined(row)) return row.rig();
			else return null;
	},
	
	// __constructor__
	initialize: function(container, o, ro) {	
		table = $E("table", container);
		this.table = table;
		this.setOptions(o);
		
		// remove first column?
		if ($defined(o.deleteFirstColumn) && o.deleteFirstColumn) this.removeFirstColumn(table);
		
		//
		// Set up live rows
		//
		
		var rawRows = $ES("tr", table);
		
		// remove header row
		
		if (rawRows.getElements("th").length > 0) rawRows.splice(0,1);
		
		// create live rows
		rawRows.each( function(row, i) {
			this.rows[i] = new Choqolate.Base.LiveRow(this, row, ro);
		}, this);
		
		this.addEvent('onRowUpdate', function(row){
			row.dataRow.getElements('button').each( function(btn) {
				ieButtonHackSetObject(btn, row.id);
			}.bind(this));
		});
		
		// determine table width
		this.width = $ES("td", this.rows[0].dataRow).length;
			
		
		// rig sort headers
		var that = this;
		this.table.getElements('th').each( function(header, i) {
			header.addEvent('click', function() { that.sort(i); });
		}.bind(this));
	},

	/*
		Function: addRow
		Adds a new (data) row at the end of the table and creates a corresponding LiveRow object.
		
		Returns:
			the LiveRow object of the newly created row
	*/
	addRow: function(id) {
		// TODO: index rows by ID, not 1..n
		
		// create a copy of a row tag. Create the liverow object
		l = this.rows.length;
		
		// PROBLEM: what if the table is empty?
		var newRow = this.rows[0].dataRow.clone();
		this.rows[l] = new Choqolate.Base.LiveRow(this, newRow);
		
		// update
		this.rows[l].update({0: id});
		
		// display fx		
		newRow.effect('opacity', {duration: 1000}).start(0,1);
		if (this.rows[l-1].rigged) newRow.injectAfter(this.rows[l-1].ajaxRow);
			else newRow.injectAfter(this.rows[l-1].dataRow);
		
		return this.rows[l];
	},
	
	/*
		Function: removeRow
		Using the ID value stored in the column identified by idColumnCSS, finds the row and removes it. 
		
		Returns:
			True if successful, otherwise false.
	*/
	removeRow: function(id) {
		var row = this.findRow(id);
		if (!$defined(row)) return false;
		
		var that = this;
		row.dataRow.effects({duration: 1000}).start({'opacity': 0}).chain( function() {
			row.dataRow.remove();
			that.rows.remove(row);
		});
		return true;
	},
	
	/*
		Function findRow
		Finds a LiveRow object using its ID
		
		Returns:
			null if not found, otherwise the LiveRow object
	*/
	findRow: function(id) {
		this.rows.each( function(row, i) {
			if (row.id == id) return row;
		});
		return null;
	},
	
	/*
		Function: sort
		Sort ith column in ascending order. If the column was already sorted in ascending, it will be sorted in descending instead. Also features automatic datatype detection. Odd rows automatically get a "oddRow" class added to them.
		
		Todo:
			More Datatype validation?
	*/
	sort: function(c) {
		// fade effect
		var that = this;
		this.table.effect('opacity').start(0.5).chain( function() {
			// determine sort order
			if (c == that.sorts.last) that.sorts.asc = (that.sorts.asc == false);
			else {
				that.sorts.last = c;
				that.sorts.asc = true;
			}
				
			// scrape data
			var data = [];
			that.rows.each( function(row, i) {
				raw = row.dataRow.getChildren()[c].getText();
				if (raw.test("^[0-9]*$")) {
					// number
					value = raw.toInt();
				} else {
					// string
					value = raw;
				}
			
				data[i] = {
					'value'	: value,
					'row'	: row
				}
			});
			
			// comparison function:
			if (that.sorts.asc) comp = function(a,b) { return (data[i].value < data[j].value) };
				else comp = function(a,b) { return (data[i].value > data[j].value) };
			
			
			// insertion sort
			for (i = that.rows.length - 1; i >= 0; i--) {
				for (j = i + 1; j < that.rows.length; j++) {
					// here, you can define your custom sorting function
					if (comp(data[i].value, data[j].value)) break;
				}
				
				if (j-i > 1) {
					// sort to new position
					data.splice(j,0, data[i]);
					data.splice(i,1);
				}
			}
			
			// rearrange rows
			var odd = true;
			var rigged = data[0].row.rigged;
			for (i = 1; i < data.length; i++) {
				data[i].row.dataRow.remove();
				if (rigged) data[i].row.dataRow.injectAfter(data[i-1].row.ajaxRow);
					else   	data[i].row.dataRow.injectAfter(data[i-1].row.dataRow);
				
				if (odd) 
					data[i].row.dataRow.addClass("oddRow");
				else
					data[i].row.dataRow.removeClass("oddRow");
				
				odd = (odd == false);
				
				// 'rigged' changes meaning!
				rigged = data[i].row.rigged;
				if (rigged) {
					data[i].row.ajaxRow.remove();
					data[i].row.ajaxRow.injectAfter(data[i].row.dataRow);
				}
				
			}
			
			// fade effect
			this.start(1);
		});
	},
	
	// for debug purposes
	toString: function() {

	}
});
Choqolate.LiveTable.implement(new Events);
Choqolate.LiveTable.implement(new Options);


/* 
	Class: UI
	A parent class structure, a "canvas" to which widgets can be added.
	
	Argument:
		seed - A representative Element() that will hold all the widgets. It is NOT a canvas,
		 the element keeps a DOM structure to the widget added to it.
		
	Example:
		
*/
Choqolate.UI = new Class({
	seed : Element(),
	initialize: function(seed) {
		this.seed = seed;
	},
	
	//	Function: add
	//	Adds a widget to this UI canvas
	//
	//	Parameter:
	//		widget - any of the Choqolate.UI widgets. See this: <UI.Base>
	// 
	//	Returns:
	//		The widget
	add: function(widget) {
		this.seed.adopt(widget.element);
		return widget;
	}
});


/*	Class: UI.Base
	The base class for all the UI Widgets. All Widgets derive from this and as such, they all share
	the same constructor. On initialization creates a div element. The element is shown after
	being added to an UI.
	
	Widgets:
		o <UI.Overlay>
		
	Properties:
		ui  - The <Choqolate.UI> parent canvas.
		element - The top Element() that wraps up and contains all HTML elements of the widget
		options - bunch of options.
		fx		- various effects classes
		
	Options:
		show - if true, the element is shown immediately (using fadeIn). Default is true.
		
	Example:
		> // create canvas
		> var ui = new Choqolate.UI($('contents'));
		> // add an overlay
		> var overlay = new Choqolate.UI.Overlay().addTo(ui);
		> // alternatively:
		> var overlay = ui.add(new Choqolate.UI.Overlay());
		
*/
Choqolate.UI.Base = new Class({
	ui		: Choqolate.UI(),
	element	: Element(),
	options : {
		'show': true,
		'fx'  : true,
		'opacity': 1
	},
	fx		: Object(),
	
	initialize: function(o) {
		this.setOptions(o);
		this.element = new Element('div');
		this.element.setOpacity(0);
		this.fx      = new Object();
		this.fx.show = new Fx.Style(this.element, 'opacity', {duration: 800});
	},
	
	// Function: addTo
	// Adds the widget to a UI canvas
	//
	// Returns:
	//		The widget
	addTo: function(ui) {
		this.ui = ui;
		if (this.options.show) this.show();
		return ui.add(this);
	},
	
	// Function: destroy
	// Deletes the element from the DOM.
	destroy: function() {
		this.hide();
		this.element.remove.delay(1000, this.element);
	},
	
	
	/*
		Functions: Visibility
		appear 		- instantly show the widget
		disappear 	- instantly hide the widget
		fadeIn 		- shows the widget, using an opacity effect
		fadeOut 	- slowly fades out the widget
		show		- depending on the options.fx, use appear or fadeIn
		hide		- similarly as above
		
	*/
	appear: 	function() { this.fx.show.set(this.options.opacity) },	
	disappear: 	function() { this.fx.show.set(0) },
	fadeIn: 	function() { this.fx.show.start(this.options.opacity) },
	fadeOut: 	function() { this.fx.show.start(0) },
	show: 		function() { this.options.fx ? this.fadeIn() : this.appear() },
	hide:		function() { this.options.fx ? this.fadeOut() : this.disappear() }
});
Choqolate.UI.Base.implement(new Options);

/*
	Class: UI.Overlay
	Creates a centered DIV Element in the middle of the screen, floating above all elements.
	
	Extends:
		<UI.Base>
	
	Arguments:
		 'o' - options, see below
		
	Options:
		style - Object with pair of CSS-property: value
		width - width in pixels, default is 500
		height - height in pixels, default is 200
		For generic options, see <UI.Base>
		
	Example:
		Overlay is a very basic, but hence flexible widget. A possible use is to extend it with one of the
		live functionalities. E.g.
		
		> var ui = new Choqolate.UI($('contents'));
		> var overlay = new Choqolate.UI.Overlay().addTo(ui);
		>
		> overlay.ajaxify();
		> overlay.rig();
		> //overlay is now ready to receive ajax request;
*/

Choqolate.UI.Overlay = Choqolate.UI.Base.extend({
	// Section: Properties
	// options - options of the
	// element - actual Element being overlayed
	options	: {
		'style': {
			'z-index' 	: 100,
			'position'	: 'absolute',
			'overflow'	: 'hidden'
		},
		'class'	  : '',
		'height'  : 200,
		'width'   : 300,
	},
	// add another fx
	
	initialize: function(o) {
		this.parent(o);

		this.element.setStyles(this.options.style);	
		this.element.addClass(this.options.class);
		
		this.element.setStyles({
			'height'  : this.options.height,
			'width'	  : this.options.width,
			'top'	  : window.getScrollTop() + (window.getHeight() - this.options.height) / 2,
			'left'	  : window.getScrollLeft() + (window.getWidth() - this.options.width) / 2
		});
	},
	
	// Function: ajaxify
	// Enables ajax behaviour for this widget, defines events for some default behaviour. Uses
	// <Base.AjaxWrapped> for this.
	ajaxify: function(o) {
		$extend(this, new Choqolate.Base.AjaxWrapped(this.element, o));
		
		// add events
		this.addEvent('onCancel', this.hide); 
		

		this.addEvent('onReceive', function() {
			this.wrapper.effect('height', {duration: 1000, transition: Fx.Transitions.Bounce.easeOut}).start(this.getHeight() + this.getMinimizedHeight());
		}.bind(this));
		this.addEvent('onMinimize', function() {
			this.wrapper.effect('height', {duration: 1000, transition: Fx.Transitions.Bounce.easeOut}).start(this.getMinimizedHeight());
		}.bind(this));
		this.addEvent('onMaximize', function() {
			this.wrapper.effect('height', {duration: 1000, transition: Fx.Transitions.Bounce.easeOut}).start(this.getHeight() + this.getMinimizedHeight());
		}.bind(this));
	}
});



/*
	Class: UI.Dialog
	Extended and much more powerful version of UI.Overlay, with lots of various options. 
	Dialogs can be resizable, draggable by a handle	and fully skinnable. See examples and notes
	on how to skin Dialogs. 
	
	Extends:
		<UI.Overlay>
	
	Arguments:
		 title	- title that goes into the title bar
		 'o' 	- options, see below and <UI.Overlay>
	Options:
		size  - an object with two variables .x and .y, each array defining boundaries on size of the dialog
		modal - if true, a backdrop widget will be created as well
		
	Events:
		onClose - fired when the close button is clicked
		
	Skinning:
		To make skinning easier, all elements have been defaultly assigned classes, hence when creating
		Dialog, simply pass the 'class' parameter to identify the parent container (or use the default
		'UIDialog') and reference the elements inside it using these classes:
		
			top 	- DIV, the top bar graphics, dialog title goes here
			handle  - DIV, secondary top bar graphics, use for e.g. top right corners
			content - DIV, where content of the dialog is stored
			bottom  - DIV, the bottom part of the dialog
			corner	- A, used for resizing, located on the bottom right
			close   - A, the close button
			minimize- A, the minimize button
			
	Notes:
		*Dialog construction*
		
		The dialog is constructed using 3 block DIV elements, called 'top','content' and 'bottom'. 'top'
		has another div called 'handle' which drags the element and contains two A elements, for close
		and minimize. Content is empty by default and if ajaxified, it is converted into <Base.AjaxOutput>
		object.
		
		*Dimensions*
		
		Due to CSS implementation, the height of the dialog applies to its *content* element and is set to the
		_content_ element. The width of the content and container elements coincide, but is set to the _container_
		element. This is done by setting custom 'onDrag' in makeResizable().
		
*/
Choqolate.UI.Dialog = Choqolate.UI.Overlay.extend({
	handle	: Element(),
	top 	: Element(),
	bottom 	: Element(),
	corner	: Element(),
	close	: Element(),
	minimize: Element(),
	content : Element(),
	backdrop: Element(),
	options : {
		size : {
			x : [200,600],
			y : [100,400]
		},
		modal: false
	},
	
	// constructor
	initialize: function(title, o) {	
		this.parent(o);
		if (this.options.modal) this.backdrop = new Choqolate.UI.Backdrop();
		
		// reset height
		this.element.setStyles({'height'  : ''})

		// create top, bottom and content
		this.top	= new Element('div', { class : 'top' }).setText(title);
		this.content= new Element('div', { class : 'content' });
		this.bottom	= new Element('div', { class : 'bottom' });
		
		// create handle, corner and buttons
		this.handle = new Element('div', { class: 'handle'});
		this.corner = new Element('a',   { class: 'corner'});
		this.close  = new Element('a',   { class: 'close'});
		this.minimize = new Element('a', { class: 'minimize'});
		
		// resize and drag - about resize, see notes
		this.element.makeDraggable({'handle': this.handle});
		this.content.makeResizable({
			'handle'	: this.corner, 
			'limit' 	: this.options.size,
			'onDrag'	: function() {
				this.element.setStyle('width', this.content.getStyle('width'));
			}.bind(this)
		});
		
		// new fx
		this.fx.height = new Fx.Style(this.content, 'height', {duration: 800});
		
		// fuse together
		this.handle.adopt(this.close);
		this.handle.adopt(this.minimize);
		this.top.adopt(this.handle);
		this.bottom.adopt(this.corner);
		this.element.adopt(this.top);
		this.element.adopt(this.content);
		this.element.adopt(this.bottom);
		
		// assign actions
		this.close.addEvent('click', function() {
			if (this.options.modal) this.backdrop.destroy();
			this.destroy();
			this.fireEvent('onClose');
		}.bind(this));
		
		this.minimize.addEvent('click', function() {
			this.fx.height.start(0);
		}.bind(this));
	},
	
	// Function: ajaxify
	// Enables ajax behaviour for this object. Unlike Overlay, Dialog uses simpler <Base.AjaxOutput>.
	ajaxify: function(o) {
		$extend(this, new Choqolate.Base.AjaxOutput(this.content, o));
		// add events
		this.addEvent('onCancel', this.hide); 
		
		this.addEvent('onReceive', function() {
			this.content.effect('height', {duration: 1000, transition: Fx.Transitions.Bounce.easeOut}).start(this.getHeight());
		}.bind(this));
		this.addEvent('onMinimize', function() {
			this.wrapper.effect('height', {duration: 1000, transition: Fx.Transitions.Bounce.easeOut}).start(this.getMinimizedHeight());
		}.bind(this));
		this.addEvent('onMaximize', function() {
			this.wrapper.effect('height', {duration: 1000, transition: Fx.Transitions.Bounce.easeOut}).start(this.getHeight() + this.getMinimizedHeight());
		}.bind(this));
	},
	
	addTo: function(ui) {
		if (this.options.modal) this.backdrop.addTo(ui);
		return this.parent(ui);
	}
});

/*
	Class: UI.Backdrop
	Creates a backdrop on the current screen.
	
	Extends:
		<UI.Overlay>
	
	Arguments:
		color - color of the backdrop, default is black
		opacity - opacity of the backdrop from 0 (transparent) to 1 (opaque), default is 0.75
*/
Choqolate.UI.Backdrop = Choqolate.UI.Overlay.extend({
	options: {
		opacity	: '0.75',
		color	: 'black'
	},
	
	initialize: function(color, opacity) {
		this.parent();
	
		if ($defined(color)) this.options.color = color;
		if ($defined(opacity)) this.options.opacity = opacity;
		this.element.setStyles({
			'background': this.options.color,
			'top'		: 0,
			'left'		: 0,
			'height'	: window.getScrollHeight(),
			'width'		: window.getScrollWidth(),
		});
	}
});


/**** HACKS START HERE (look away now) ****/

if(!$defined(console)){
	var Console = new Class({
		log: function(msg){
			alert(msg);
		}
	});
	var console = new Console();
};

function ieButtonHackValue(btn)
{
	return $(btn.getProperty('name')+'-hack').getProperty('value');
};

function ieButtonHackSetValue(btn, val)
{
	btn.setProperty('onClick', '');
	btn.removeEvents('click');
	btn.addEvent('click', function(){
		ieButtonHack(btn, val);
	});
};

function ieButtonHackSetObject(btn, id){
	// Get the old value from the class
	classes = btn.getProperty('class').split(' ');
	classes.forEach(function(cls) {
		if(cls.contains('buttonVal-')){
			// This is the value.
			valz = cls.replace('buttonVal-','').split('-');
			valz[valz.length - 1] = id;
			
			val = valz.join('-');
			ieButtonHackSetValue(btn, val);
			btn.removeClass(cls);
			btn.addClass('buttonVal-'+val);
		}
	});
};

function ieButtonHackCleanup(){
	$$('button').each(function(b){
		b.setProperty('disabled', 0);
		hdn = $(b.getProperty('name')+'-hack');
		if(hdn)	hdn.remove();
	});
};

function ieButtonHack(el, val){
	btn = $(el);
	
	$$('button').each(function(b){
		if ((b.getProperty('name') != btn.getProperty('name')) ||
			(b.getProperty('value') != btn.getProperty('value'))) {
			b.setProperty('disabled', 0);
		};
	});
	
	new Element('input', {
		id: btn.getProperty('name')+'-hack',
		name: btn.getProperty('name'),
		type: 'hidden',
		value: val
	}).injectAfter(btn);
};

